﻿using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Web;
using Microsoft.IdentityModel.Tokens;
using LDM.Framework.Application;
using IdentityModel;

namespace LDM.Framework;

/// <summary>
/// JWT操作帮助类
/// </summary>
public class JWTHelper
{
    /// <summary>
    /// 对称加密秘钥
    /// </summary>
    public static SymmetricSecurityKey SymmtricKey { get; private set; }

    public JWTHelper()
    {
        var key = App.GetConfig("Jwt", "SecurityKey") ?? Constant.DefaultJwtSymmtricKey;
        SymmtricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
    }

    /// <summary>
    /// Audience: api
    /// </summary>
    public static string AUDIENCE = App.GetConfig("Jwt", "Audience");

    /// <summary>
    /// Issuer: 
    /// </summary>
    public static string ISSUER = App.GetConfig("Jwt", "Issuer");

    /// <summary>
    /// 创建Token
    /// </summary>
    /// <param name="payload">payload信息</param>
    /// <param name="expiresAtUtc">指定过期的UTC时间</param>
    /// <returns></returns>
    public string CreateToken(JWTPayloadInfo payload, DateTime expiresAtUtc)
    {
        if (payload == null)
            throw new ArgumentNullException(nameof(payload));

        var tokenHandler = new JwtSecurityTokenHandler();
        var claims = new List<Claim>();
        if (!string.IsNullOrEmpty(payload.UserId))
            claims.Add(new Claim(JwtClaimTypes.Id, payload.UserId.ToString()));
        if (!string.IsNullOrEmpty(payload.Name))
            claims.Add(new Claim(JwtClaimTypes.Name, payload.Name));
        if (!string.IsNullOrEmpty(payload.Email))
            claims.Add(new Claim(JwtClaimTypes.Email, payload.Email));
        if (!string.IsNullOrEmpty(payload.Role))
            claims.Add(new Claim(JwtClaimTypes.Role, payload.Role));
        if (!string.IsNullOrEmpty(payload.UserChannel))
            claims.Add(new Claim(JwtClaimTypes.ClientId, payload.UserChannel));
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Issuer = ISSUER,
            Audience = AUDIENCE,
            NotBefore = DateTime.Now,
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.Now.AddMinutes(60),
            SigningCredentials = new SigningCredentials(SymmtricKey, SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        var tokenString = tokenHandler.WriteToken(token);
        return tokenString;
    }

    /// <summary>
    /// 从token中读取出payload的信息
    /// </summary> 
    /// <returns></returns>
    public JWTPayloadInfo ReadPayload(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();

        var payloadInfo = new JWTPayloadInfo();
        SecurityToken validatedToken;
        var tokenParameter = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = SymmtricKey,
            ValidateLifetime = true,
            ValidateIssuer = true,
            ValidIssuer = ISSUER,
            ValidateAudience = true,
            ValidAudience = AUDIENCE,
        };
        var tokenObj = tokenHandler.ValidateToken(token, tokenParameter, out validatedToken);
        var theToken = validatedToken as JwtSecurityToken;
        if (theToken == null || theToken.Claims == null || !theToken.Claims.Any())
            return payloadInfo;
        var userId = theToken.Claims.FirstOrDefault(x => x.Type.Equals(JwtClaimTypes.Id, StringComparison.OrdinalIgnoreCase))?.Value;
        payloadInfo.UserId = DecryptWithUrlDecode<string>(userId);
        payloadInfo.Name = theToken.Claims.FirstOrDefault(x => x.Type.Equals(JwtClaimTypes.Name, StringComparison.OrdinalIgnoreCase))?.Value;
        payloadInfo.Email = theToken.Claims.FirstOrDefault(x => x.Type.Equals(JwtClaimTypes.Email, StringComparison.OrdinalIgnoreCase))?.Value;
        payloadInfo.Role = theToken.Claims.FirstOrDefault(x => x.Type.Equals(JwtClaimTypes.Role, StringComparison.OrdinalIgnoreCase))?.Value;
        payloadInfo.UserChannel = (theToken.Claims.FirstOrDefault(x => x.Type.Equals(JwtClaimTypes.ClientId, StringComparison.OrdinalIgnoreCase))?.Value) ?? string.Empty;
        return payloadInfo;
    }

    /// <summary>
    /// 将参数解密并转成对应的数据类型
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    private T DecryptWithUrlDecode<T>(string source, string key = "")
    {
        if (string.IsNullOrEmpty(source))
        {
            return default;
        }
        try
        {
            var decodeStr = string.IsNullOrEmpty(key) ? Encrypt.DesDecrypt(HttpUtility.UrlDecode(source).Replace(" ", "+")) : Encrypt.DesDecrypt(HttpUtility.UrlDecode(source).Replace(" ", "+"), key);
            if (string.IsNullOrEmpty(decodeStr))
            {
                return default(T);
            }
            return (T)System.Convert.ChangeType(decodeStr, typeof(T));
        }
        catch (Exception)
        {
            return default(T);
        }
    }

}

/// <summary>
/// JWT payload部分
/// </summary>
public class JWTPayloadInfo
{
    /// <summary>
    /// Audience
    /// </summary>
    internal const string AUDIENCE = "api";

    /// <summary>
    /// Issuer
    /// </summary>
    internal const string ISSUER = "LDM.Dream";

    /// <summary>
    /// 当为微信小程序时，值为openid
    /// </summary>
    public string Subject { get; set; }
    /// <summary>
    /// 用户id
    /// </summary>
    public string UserId { get; set; }
    /// <summary>
    ///用户名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 用户角色
    /// </summary>
    public string Role { get; set; }

    /// <summary>
    /// 用户类型
    /// </summary>
    public int UserType { get; set; }
    

    /// <summary>
    /// 用户渠道
    /// 1微信小程序 0Pc端
    /// </summary>
    public string UserChannel { get; set; }

    /// <summary>
    /// End-User's preferred e-mail address. Its value MUST conform to the RFC 5322 [RFC5322]
    /// addr-spec syntax. The relying party MUST NOT rely upon this value being unique
    /// </summary>
    public string Email { get; set; }

    /// <summary>
    /// Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0
    ///     client_id of the Relying Party as an audience value. It MAY also contain identifiers
    ///     for other audiences. In the general case, the aud value is an array of case sensitive
    ///     strings. In the common special case when there is one audience, the aud value
    ///     MAY be a single case sensitive string.
    /// </summary>
    public string Audience { get; set; } = AUDIENCE;

    /// <summary>
    /// Issuer Identifier for the Issuer of the response. The iss value is a case sensitive
    ///     URL using the https scheme that contains scheme, host, and optionally, port number
    ///     and path components and no query or fragment components.
    /// </summary>
    public string Issuer { get; set; } = ISSUER;
}
